// Copyright 2019 Google LLC.
#ifndef TextWrapper_DEFINED
#define TextWrapper_DEFINED

#include <string>
#include "include/core/SkSpan.h"
#include "modules/skparagraph/src/TextLine.h"

namespace skia {
namespace textlayout {
class ParagraphImpl;

class TextWrapper {
    class ClusterPos {
    public:
        ClusterPos() : fCluster(nullptr), fPos(0) {}
        ClusterPos(Cluster *cluster, size_t pos) : fCluster(cluster), fPos(pos) {}
        inline Cluster *cluster() const
        {
            return fCluster;
        }
        inline size_t position() const
        {
            return fPos;
        }
        inline void setPosition(size_t pos)
        {
            fPos = pos;
        }
        void clean()
        {
            fCluster = nullptr;
            fPos = 0;
        }
        void move(bool up)
        {
            fCluster += up ? 1 : -1;
            fPos = up ? 0 : fCluster->endPos();
        }

    private:
        Cluster *fCluster;
        size_t fPos;
    };
    class TextStretch {
    public:
        TextStretch() : fStart(), fEnd(), fWidth(0), fWidthWithGhostSpaces(0) {}
        TextStretch(Cluster *s, Cluster *e, bool forceStrut)
            : fStart(s, 0), fEnd(e, e->endPos()), fMetrics(forceStrut), fWidth(0), fWidthWithGhostSpaces(0)
        {
            for (auto c = s; c <= e; ++c) {
                if (auto r = c->runOrNull()) {
                    fMetrics.add(r);
                }
                if (c < e) {
                    fWidth += c->width();
                }
            }
            fWidthWithGhostSpaces = fWidth;
        }

        inline SkScalar width() const
        {
            return fWidth;
        }
        SkScalar widthWithGhostSpaces() const
        {
            return fWidthWithGhostSpaces;
        }
        inline Cluster *startCluster() const
        {
            return fStart.cluster();
        }
        inline Cluster *endCluster() const
        {
            return fEnd.cluster();
        }
        inline Cluster *breakCluster() const
        {
            return fBreak.cluster();
        }
        inline InternalLineMetrics &metrics()
        {
            return fMetrics;
        }
        inline size_t startPos() const
        {
            return fStart.position();
        }
        inline size_t endPos() const
        {
            return fEnd.position();
        }
        bool endOfCluster()
        {
            return fEnd.position() == fEnd.cluster()->endPos();
        }
        bool endOfWord()
        {
            return endOfCluster() && (fEnd.cluster()->isHardBreak() || fEnd.cluster()->isSoftBreak());
        }

        void extend(TextStretch &stretch)
        {
            fMetrics.add(stretch.fMetrics);
            fEnd = stretch.fEnd;
            fWidth += stretch.fWidth;
            stretch.clean();
        }

        bool empty()
        {
            return fStart.cluster() == fEnd.cluster() && fStart.position() == fEnd.position();
        }

        void setMetrics(const InternalLineMetrics &metrics)
        {
            fMetrics = metrics;
        }

        void extend(Cluster *cluster)
        {
            if (fStart.cluster() == nullptr) {
                fStart = ClusterPos(cluster, cluster->startPos());
            }
            fEnd = ClusterPos(cluster, cluster->endPos());
            // TODO: Make sure all the checks are correct and there are no unnecessary checks
            auto &r = cluster->run();
            if (!cluster->isHardBreak() && !r.isPlaceholder()) {
                // We ignore metrics for \n as the Flutter does
                fMetrics.add(&r);
            }
            fWidth += cluster->width();
        }

        void extend(Cluster *cluster, size_t pos)
        {
            fEnd = ClusterPos(cluster, pos);
            if (auto r = cluster->runOrNull()) {
                fMetrics.add(r);
            }
        }

        void startFrom(Cluster *cluster, size_t pos)
        {
            fStart = ClusterPos(cluster, pos);
            fEnd = ClusterPos(cluster, pos);
            if (auto r = cluster->runOrNull()) {
                // In case of placeholder we should ignore the default text style -
                // we will pick up the correct one from the placeholder
                if (!r->isPlaceholder()) {
                    fMetrics.add(r);
                }
            }
            fWidth = 0;
        }

        void saveBreak()
        {
            fWidthWithGhostSpaces = fWidth;
            fBreak = fEnd;
        }

        void restoreBreak()
        {
            fWidth = fWidthWithGhostSpaces;
            fEnd = fBreak;
        }

        void shiftBreak()
        {
            fBreak.move(true);
        }

        void trim()
        {
            if (fEnd.cluster() != nullptr && fEnd.cluster()->owner() != nullptr &&
                fEnd.cluster()->runOrNull() != nullptr && fEnd.cluster()->run().placeholderStyle() == nullptr &&
                fWidth > 0) {
                fWidth -= (fEnd.cluster()->width() - fEnd.cluster()->trimmedWidth(fEnd.position()));
            }
        }

        void trim(Cluster *cluster)
        {
            SkASSERT(fEnd.cluster() == cluster);
            if (fEnd.cluster() > fStart.cluster()) {
                fEnd.move(false);
                fWidth -= cluster->width();
            } else {
                fEnd.setPosition(fStart.position());
                fWidth = 0;
            }
        }

        void clean()
        {
            fStart.clean();
            fEnd.clean();
            fWidth = 0;
            fMetrics.clean();
        }

    private:
        ClusterPos fStart;
        ClusterPos fEnd;
        ClusterPos fBreak;
        InternalLineMetrics fMetrics;
        SkScalar fWidth;
        SkScalar fWidthWithGhostSpaces;
    };

public:
    TextWrapper()
    {
        fLineNumber = 1;
        fHardLineBreak = false;
        fExceededMaxLines = false;
    }

    using AddLineToParagraph =
        std::function<void(TextRange textExcludingSpaces, TextRange text, TextRange textIncludingNewlines,
        ClusterRange clusters, ClusterRange clustersWithGhosts, SkScalar AddLineToParagraph, size_t startClip,
        size_t endClip, SkVector offset, SkVector advance, InternalLineMetrics metrics, bool addEllipsis)>;
    void breakTextIntoLines(ParagraphImpl *parent, SkScalar maxWidth, const AddLineToParagraph &addLine);

    SkScalar height() const
    {
        return fHeight;
    }
    SkScalar minIntrinsicWidth() const
    {
        return fMinIntrinsicWidth;
    }
    SkScalar maxIntrinsicWidth() const
    {
        return fMaxIntrinsicWidth;
    }
    bool exceededMaxLines() const
    {
        return fExceededMaxLines;
    }

private:
    TextStretch fWords;
    TextStretch fClusters;
    TextStretch fClip;
    TextStretch fEndLine;
    size_t fLineNumber;
    bool fTooLongWord;
    bool fTooLongCluster;

    bool fHardLineBreak;
    bool fExceededMaxLines;

    SkScalar fHeight;
    SkScalar fMinIntrinsicWidth;
    SkScalar fMaxIntrinsicWidth;

    void reset()
    {
        fWords.clean();
        fClusters.clean();
        fClip.clean();
        fTooLongCluster = false;
        fTooLongWord = false;
        fHardLineBreak = false;
    }

    void lookAhead(SkScalar maxWidth, Cluster *endOfClusters, bool applyRoundingHack);
    void moveForward(bool hasEllipsis);
    void trimEndSpaces(TextAlign align);
    std::tuple<Cluster *, size_t, SkScalar> trimStartSpaces(Cluster *endOfClusters);
    SkScalar getClustersTrimmedWidth();
};
} // namespace textlayout
} // namespace skia

#endif // TextWrapper_DEFINED
